package ffmpeg;

import util.Common;
import lombok.Data;
import org.apache.log4j.Logger;
import org.bytedeco.javacpp.*;

import static org.bytedeco.javacpp.avcodec.*;
import static org.bytedeco.javacpp.avformat.*;
import static org.bytedeco.javacpp.avutil.*;

@Data
public class FFmpeg {
    private static final Logger logger = Logger.getLogger(FFmpeg.class);

    private  AVFormatContext avFormatContext;
    private  AVCodecContext avCodecContext;
    private AvStreamIndexInfo avStreamIndex;

    public double r2d(AVRational avRational){
       if (avRational.den() == 0) return 0;
       return (double) avRational.num() / (double) avRational.den();
    }

    public long getDuration() {
        long duration = avFormatContext.duration();  //微秒数
        return duration / 1000;
    }

    public void open(String url){
        avFormatContext = getFormatContext(url);
        avStreamIndex = getVideoStreamIndex(avFormatContext);

//        av_seek_frame(avFormatContext, -1 , 0 * AV_TIME_BASE, AVSEEK_FLAG_ANY);

        if (avStreamIndex.getAudioStreamIndex() == -1 && avStreamIndex.getVideoStreamIndex() == -1) {
            logger.error("video not video  and audio");
            return;
        }

        avCodecContext = getCodecContext(avFormatContext, avStreamIndex.getVideoStreamIndex());
    }



    /**
     * 解析 ffmpeg 里面 pps 和 sps
     * @return
     */
    public SpsPpsInfo parseSpsPps(){
        byte[] data = getExtradata(avStreamIndex.getVideoStreamIndex());
        logger.info("data  " + Common.bytes2hex(data));
        SpsPpsInfo spsPpsInfo = new SpsPpsInfo();
        int index = 0;
        //1 跳过前4个字节
        index = index + 4;
        //2 第五位为 nalu 所占字节数
        int naluLength = (data[index++] & (byte) 0x03) + 1;
        logger.info("naluLength"+naluLength);
        boolean spsStatus = false;
        boolean ppsStatus = false;
        logger.info("index === " + index + "   len ==== " + data.length);
        //3 第6位 sps 和 pps 个数
        int sps_pps_count = (data[index++] & (byte) 0x1f);
        logger.info("sps_pps_count" + sps_pps_count);
        while (sps_pps_count-- > 0 || !spsStatus || !ppsStatus) {
            // 4 第7位和第8位 表示 sps 或者 pps 长度
            int size = data[index++] << 8 | data[index++];
            logger.info("size" + size);
            if (index > data.length) {
                logger.error("数据异常");
                return spsPpsInfo;
            }

            byte nalu_type = data[index];
            nalu_type = (byte) (nalu_type & (byte) 0x1f);
            logger.info("nalu_type" + nalu_type);

            // 解析 sps
            if (nalu_type == 7) {
                spsStatus = true;
                byte[] sps = new byte[size];
                int spsIndex = 0;
                while (spsIndex < size) {
                    sps[spsIndex++] = data[index++];
                }
                logger.info("sps " + Common.bytes2hex(sps));
                spsPpsInfo.setSps(sps);
            }
            // 解析 pps
            if (nalu_type == 8) {
                ppsStatus = true;
                byte[] pps = new byte[size];
                int ppsIndex = 0;
                while (ppsIndex < size) {
                    pps[ppsIndex++] = data[index++];
                }
                logger.info("pps " + Common.bytes2hex(pps));
                spsPpsInfo.setPps(pps);
            }
            index++;//nalu_type也算一位,所以index向后一位
        }

        return spsPpsInfo;
    }

    public byte[] getExtradata(int index){
        AVCodecParameters avCodecParameters =  avFormatContext.streams(index).codecpar();
        int size = avCodecParameters.extradata_size();
        byte[] data = new byte[size];
        avCodecParameters.extradata().get(data);
        return data;

    }

    public AVPacket getAvPacket() {
        return getSinglePacket(avCodecContext, avFormatContext, avStreamIndex.getVideoStreamIndex());
    }

    /**
     * 流媒体解封装后得到多个流组成的数组，该方法找到视频流咋数组中的位置
     * @param pFormatCtx
     * @return
     */
    private AvStreamIndexInfo getVideoStreamIndex(AVFormatContext pFormatCtx) {
        AvStreamIndexInfo avStreamIndex = new AvStreamIndexInfo();
        // 解封装后有多个流，找出视频流是第几个
        for (int i = 0; i < pFormatCtx.nb_streams(); i++) {
            int streamIndex = pFormatCtx.streams(i).codecpar().codec_type();
            if (streamIndex == AVMEDIA_TYPE_VIDEO) {
                avStreamIndex.setVideoStreamIndex(i);
            }else if (streamIndex == AVMEDIA_TYPE_AUDIO) {
                avStreamIndex.setAudioStreamIndex(i);
            }
        }
        return avStreamIndex;
    }

    /**
     * 生成解封装上下文
     * @param url
     * @return
     */
    private AVFormatContext getFormatContext(String url) {
        // 解封装上下文
        AVFormatContext pFormatCtx = new avformat.AVFormatContext(null);

        // 打开流媒体
        if (avformat_open_input(pFormatCtx, url, null, null) != 0) {
            logger.error("打开媒体失败");
            return null;
        }

        // 读取流媒体数据，以获得流的信息
        if (avformat_find_stream_info(pFormatCtx, (PointerPointer<Pointer>) null) < 0) {
            logger.error("获得媒体流信息失败");
            return null;
        }

        // 打开指定流媒体，进行解封装，得到解封装上下文


        // 控制台打印流媒体信息
        av_dump_format(pFormatCtx, 0, url, 0);
        return pFormatCtx;
    }



    /**
     * 生成解码上下文
     * @param pFormatCtx
     * @param videoStreamIndex
     * @return
     */
    private avcodec.AVCodecContext getCodecContext(AVFormatContext pFormatCtx, int videoStreamIndex) {
        //解码器
        avcodec.AVCodec pCodec;

        // 得到解码上下文
        avcodec.AVCodecContext pCodecCtx = pFormatCtx.streams(videoStreamIndex).codec();

        // 根据解码上下文得到解码器
        pCodec = avcodec_find_decoder(pCodecCtx.codec_id());

        if (pCodec == null) {
            return null;
        }

        // 用解码器来初始化解码上下文
        if (avcodec_open2(pCodecCtx, pCodec, (avutil.AVDictionary)null) < 0) {
            return null;
        }

        return pCodecCtx;
    }

    private AVPacket getSinglePacket(avcodec.AVCodecContext pCodecCtx, AVFormatContext pFormatCtx, int videoStreamIndex){
        AVPacket avPacket = new AVPacket();
        try {
            if (av_read_frame(pFormatCtx, avPacket) >= 0) {
                return avPacket;
            }
        } catch (Exception e) {
            logger.error(e.getMessage());
        }
        return null;
    }


    public void seekFrame(long time){
        av_seek_frame(avFormatContext, -1 , time, AVSEEK_FLAG_BACKWARD);
        avcodec_flush_buffers(getCodecContext(avFormatContext, avStreamIndex.getVideoStreamIndex()));
    }


    /**
     * 取一帧然后解码
     * @param pCodecCtx
     * @param pFormatCtx
     * @param videoStreamIndex
     * @return
     */
    private avutil.AVFrame getSingleFrame(avcodec.AVCodecContext pCodecCtx, AVFormatContext pFormatCtx, int videoStreamIndex) {
        // 分配帧对象
        avutil.AVFrame pFrame = av_frame_alloc();

        // frameFinished用于检查是否有图像
        int[] frameFinished = new int[1];

        // 是否找到的标志
        boolean exists = false;

        avcodec.AVPacket packet = new avcodec.AVPacket();

        try {
            // 每一次while循环都会读取一个packet
            while (av_read_frame(pFormatCtx, packet) >= 0) {
                // 检查packet所属的流是不是视频流
                if (packet.stream_index() == videoStreamIndex) {
                    // 将AVPacket解码成AVFrame
                    avcodec_decode_video2(pCodecCtx, pFrame, frameFinished, packet);// Decode video frame

                    // 如果有图像就返回
                    if (frameFinished != null && frameFinished[0] != 0 && !pFrame.isNull()) {
                        exists = true;
                        break;
                    }
                }
            }
        } finally {
            // 一定要执行释放操作
            av_free_packet(packet);
        }

        // 找不到就返回空
        return exists ?  pFrame : null;
    }


}
